package control

import (
	"chat_project/module"
	"chat_project/service"
	"chat_project/util"
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
	"log"
	"net/http"
	"strconv"
	"sync"
	// golang中没有实现set,需要用第三方包
	"gopkg.in/fatih/set.v0"
)

// 用来处理文字和表情包发送逻辑

// CMD类型,单聊、群聊、心跳包
const (
	CMD_SINGLE_MSG = 10
	CMD_ROOM_MSG   = 11
	CMD_HEART      = 0
)

// 定义message结构体,存放前端传递过来的json数据
type Message struct {
	Id      int64  `json:"id,omitempty" form:"id"`           //消息ID
	Userid  int64  `json:"userid,omitempty" form:"userid"`   //谁发的
	Cmd     int    `json:"cmd,omitempty" form:"cmd"`         //群聊还是私聊
	Dstid   int64  `json:"dstid,omitempty" form:"dstid"`     //对端用户ID/群ID
	Media   int    `json:"media,omitempty" form:"media"`     //消息按照什么样式展示
	Content string `json:"content,omitempty" form:"content"` //消息的内容
	Pic     string `json:"pic,omitempty" form:"pic"`         //预览图片
	Url     string `json:"url,omitempty" form:"url"`         //服务的URL
	Memo    string `json:"memo,omitempty" form:"memo"`       //简单描述
	Amount  int    `json:"amount,omitempty" form:"amount"`   //其他和数字相关的
}

/**
消息发送结构体
1、MEDIA_TYPE_TEXT
{id:1,userid:2,dstid:3,cmd:10,media:1,content:"hello"}
2、MEDIA_TYPE_News
{id:1,userid:2,dstid:3,cmd:10,media:2,content:"标题",pic:"http://www.baidu.com/a/log,jpg",url:"http://www.a,com/dsturl","memo":"这是描述"}
3、MEDIA_TYPE_VOICE，amount单位秒
{id:1,userid:2,dstid:3,cmd:10,media:3,url:"http://www.a,com/dsturl.mp3",anount:40}
4、MEDIA_TYPE_IMG
{id:1,userid:2,dstid:3,cmd:10,media:4,url:"http://www.baidu.com/a/log,jpg"}
5、MEDIA_TYPE_REDPACKAGR //红包amount 单位分
{id:1,userid:2,dstid:3,cmd:10,media:5,url:"http://www.baidu.com/a/b/c/redpackageaddress?id=100000","amount":300,"memo":"恭喜发财"}
6、MEDIA_TYPE_EMOJ 6
{id:1,userid:2,dstid:3,cmd:10,media:6,"content":"cry"}
7、MEDIA_TYPE_Link 6
{id:1,userid:2,dstid:3,cmd:10,media:7,"url":"http://www.a,com/dsturl.html"}

7、MEDIA_TYPE_Link 6
{id:1,userid:2,dstid:3,cmd:10,media:7,"url":"http://www.a,com/dsturl.html"}

8、MEDIA_TYPE_VIDEO 8
{id:1,userid:2,dstid:3,cmd:10,media:8,pic:"http://www.baidu.com/a/log,jpg",url:"http://www.a,com/a.mp4"}

9、MEDIA_TYPE_CONTACT 9
{id:1,userid:2,dstid:3,cmd:10,media:9,"content":"10086","pic":"http://www.baidu.com/a/avatar,jpg","memo":"胡大力"}

*/

type ChatControl struct {
}

type Node struct {
	// websocket的链接对象
	Conn *websocket.Conn
	// 字节数组, 用于把并行数据转为串行,因为conn是IO型的资源,会存在同时写的操作,为了保证顺序,否则会写乱
	DataQueue chan []byte
	//
	GroupSets set.Interface
}

var returnBool util.ReturnBool

// userid和node的映射
var clientMap map[int64]*Node = make(map[int64]*Node)

// 读写锁
var rwlocker sync.RWMutex

// 处理ws链接 ws://127.0.0.1/chat?id=1&tokenn=xxx
func (ChatControl) Chat(writer http.ResponseWriter, request *http.Request) {
	/*
		前端通过websocket发送json格式的字符串,例如用户2向用户3发送文字消息hello,需要封装为如下格式
		{id:1,userid:2,dstid:3,cmd:10,media:1,content:"hello"}
		userid:谁发的
		dstid:发给谁的
		cmd:消息用途,单聊还是群聊
		media:消息展示方式,文本格式、图片格式、语音格式
		content: 消息内容,url,amount,pic,content等
	*/
	// 1. 检验接入是否合法, token
	// 通过url获取请求参数id和token
	query := request.URL.Query()
	id := query.Get("id")
	token := query.Get("token")
	// userid从string转为int64
	userid, _ := strconv.ParseInt(id, 10, 64)
	isvalid := CheckToken(userid, token)
	upgrader := websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
		// 解决跨域问题
		CheckOrigin: func(r *http.Request) bool {
			return isvalid
		},
	}

	// 使用websocket中的方法来处理false情况
	conn, err := upgrader.Upgrade(writer, request, nil)

	if err != nil {
		log.Println(err.Error())
		return
	}
	// 获取连接conn
	node := &Node{
		Conn:      conn,
		DataQueue: make(chan []byte),
		// 创建set对象
		GroupSets: set.New(set.ThreadSafe),
	}
	// userid和node的绑定关系
	// map操作的数量比较大,因此要加上读写锁,防止并发出错
	rwlocker.Lock()
	clientMap[userid] = node
	rwlocker.Unlock()
	// 完成发送逻辑
	go sendproc(node)
	// 完成接受逻辑
	go recvproc(node)

	// 测试发送数据给前端
	SendMsg(userid, []byte("hello golang this is zhang test!"))

}

// 发送协程
func sendproc(node *Node) {
	for {
		select {
		case data := <-node.DataQueue:
			err := node.Conn.WriteMessage(websocket.TextMessage, data)
			if err != nil {
				log.Println(err.Error())
				return
			}

		}
	}
}

// 接收协程
func recvproc(node *Node) {
	for {
		_, data, err := node.Conn.ReadMessage()
		if err != nil {
			log.Println(err.Error())
			return
		}
		// 这里对data做初步处理,后续会做详细处理
		// todo 对data数据进行处理
		dispatch(data)
		fmt.Printf("recv<=%s", data)
	}
}

// 处理前端(数据发送者)发过来的json字符串数据,进行转换后发送给前端(数据接收者)
func dispatch(data []byte) {
	fmt.Println("进入dispatch逻辑")
	// 1. 解析data为message
	msg := Message{}
	// 处理json字符串
	err := json.Unmarshal(data, &msg)
	if err != nil {
		log.Println(err.Error())
		return
	}
	// 2. 根据cmd对逻辑进行处理
	switch msg.Cmd {
	case CMD_SINGLE_MSG:
		fmt.Println("发送单聊信息")
		SendMsg(msg.Dstid, data)
	case CMD_ROOM_MSG:
		fmt.Println("发送群聊信息")
		// todo 后续处理
	case CMD_HEART:
		fmt.Println("发送心跳包")
		// todo 后续处理
	}
}

func SendMsg(userid int64, msg []byte) {
	rwlocker.RLock()
	// 创建映射关系
	node, ok := clientMap[userid]
	rwlocker.RUnlock()
	if ok {
		node.DataQueue <- msg
	}

}

func CheckToken(userid int64, token string) bool {
	// userid从数据库中查询token和传入的token比较
	tmp := module.User{}
	service.DBEngin.Where("id=?", userid).Get(&tmp)
	return tmp.Token == token
}
